17 设计模式——代理模式

返回设计模式博客目录

介绍


代理(Proxy)模式:为其他对象提供一种代理以控制这个对象的访问。代理模式也叫委托模式,属于结构型模式。

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。

在软件设计中,使用代理模式的例子也很多。例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

使用场景

当无法或不想直接访问某个对象或访问某个对象存在困难时,可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

结构与实现


模式包含以下主要角色。

  • Subject(抽象主题类):接口或者抽象类,声明真实主题与代理的共同接口方法。
  • RealSubject(真实主题类):也叫做被代理类或被委托类,定义了代理所表示的真实对象,负责具体业务逻辑的执行,客户端可以通过代理类间接的调用真实主题类的方法。
  • Proxy(代理类):也叫委托类,持有对真实主题类的引用,在其所实现的接口方法中调用真实主题类中相应的接口方法执行。

其结构图如下图所示。

代理模式的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ProxyTest {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}
// 抽象主题
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
public void request() {
System.out.println("访问真实主题方法...");
}
}
// 代理
class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
public void preRequest() {
System.out.println("访问真实主题之前的预处理。");
}
public void postRequest() {
System.out.println("访问真实主题之后的后续处理。");
}
}

程序运行的结果如下:

1
2
3
访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。

示例


小明被拖欠工资,想走法律程序,找律师去申述这一个过程,使用代理模式律师就是代理者,小明就是被代理者,下面看看这样一个过程,代码应该怎样去实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public interface ILawsuit {
// 提交申请
void submit();
// 进行举证
void burden();
// 开始维护
void defend();
// 诉讼完成
void finish();
}
public class XiaoMin implements ILawsuit {
@Override
public void submit() {
// 老板欠小民工资 小小民只好申请仲裁
System.out.println("老板拖欠工资,特此申请仲裁");
}
@Override
public void burden() {
// 小民证据充足,不怕告不赢
System.out.println("这是合同书和过去一年的银行工资流水");
}
@Override
public void defend() {
// 铁证如山,辩护也没什么好说的
System.out.println("证据确凿! 不需要再说什么了");
}
@Override
public void finish() {
// 结果也是肯定的,必赢
System.out.println("诉讼成功! 判决老板即日起七天内结算工资");
}
}
public class Lawyer implements ILawsuit {
private ILawsuit mLawsuit;
public Lawyer(ILawsuit lawsuit) {
this.mLawsuit = lawsuit;
}
@Override
public void submit() {
mLawsuit.submit();
}
@Override
public void burden() {
mLawsuit.burden();
}
@Override
public void defend() {
mLawsuit.defend();
}
@Override
public void finish() {
mLawsuit.finish();
}
}
public class Client {
public static void main(String[] args) {
ILawsuit xiaomin = new XiaoMin();
// 构造代理律师
ILawsuit lawsuit = new Lawyer(xiaomin);
// 律师提交诉讼申请
lawsuit.submit();
// 律师进行举证
lawsuit.burden();
// 律师代替小民进行辩护
lawsuit.defend();
// 完成诉讼
lawsuit.finish();
}
}

从代码的角度来分,代理可以分为两种:一种是静态代理,另一种是动态代理

静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。上面的例子实现就是静态代理。

动态代理类的源码是在程序运行期间根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

下面我们实现动态代理,Java 提供了动态的代理接口 InvocationHandler,实现该接口需要重写 invoke() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DynamicProxy implements InvocationHandler {
private Object obj; // 被代理的类引用
public DynamicProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用被代理类对象的方法
Object result = method.invoke(obj, args);
return result;
}
}

使用这个动态代理修改小明案例中的客户类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Client {
public static void main(String[] args) {
// 构造一个小民
ILawsuit xiaomin = new XiaoMin();
// 构造一个动态代理
DynamicProxy proxy = new DynamicProxy(xiaomin);
// 获取被代理类小民的 ClassLoader
ClassLoader loader = xiaomin.getClass().getClassLoader();
// 动态构造一个代理者律师
ILawsuit lawyer = (ILawsuit) Proxy.newProxyInstance(loader, new Class[]{ILawsuit.class}, proxy);
// 律师提交诉讼申请
lawyer.submit();
// 律师进行举证
lawyer.burden();
// 律师代替小民进行辩护
lawyer.defend();
// 完成诉讼
lawyer.finish();
}
}

静态代理的缺点:

  • 如果接口新增一个方法,除了所有实现类(真实主题类)需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
  • 代理对象只服务于一种类型的对象,如果要服务多类型的对象。必须要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理的优点:

  • 可以通过一个代理类完成全部的代理功能,接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。当接口方法数量较多时,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
  • 动态代理的应用使我们的类职责更加单一,复用性更强。

动态代理的缺点:

  • 不能对类进行代理,只能对接口进行代理,如果我们的类没有实现任何接口,那么就不能使用这种方式进行动态代理。

根据适用范围,代理模式可以分为以下几种:

  • 远程代理:为一个对象在不同的地址空间提供局部代表,这样系统可以将Server部分的事项隐藏。
  • 虚拟代理:如果要创建一个资源消耗较大的对象,可以先用一个代理对象表示,在真正需要的时候才真正创建。
  • 保护代理:用代理对象控制对一个对象的访问,给不同的用户提供不同的访问权限。
  • 智能引用:在引用原始对象的时候附加额外操作,并对指向原始对象的引用增加引用计数。

ANDROID 源码中的实现


ANDROID 源码中多个地方都用到代理模式,比如 ActivityManagerProxy 这个代理类。其具体代理的是 ActivityManagerNative 的子类 ActivityManagerService。

ActivityManagerProxy 实现了 IActivityManager 接口,该接口定义了一些 Activity 相关的接口方法,其中有一些我们在应用开发中也时常接触到。IActivityManager 这个接口相当于代理模式中的抽象主题,那么真正的实现主题是 ActivityManagerNative 的子类 ActivityManagerService,这几个类大致的关系:

ActivityManagerProxy 实际上代理的是 ActivityManagerService,但是 ActivityManagerProxy 和 ActivityManagerService 是分别运行在不同的进程里(ActivityManagerProxy 是运行在应用的进程,而 ActivityManagerService 是运行在系统进程),所以它们之间的这个代理过程是跨进程的,这里跨进程是用到 Android 的 Binder 机制完成。不过 ActivityManagerProxy 在实际逻辑处理中并未过多地被外部类使用,因为在 Android 中管理与维护 Activity 相关信息的类是另外一个叫做 ActivityManager 的类,ActivityManager 虽然说管理着 Activity 信息,但是实质上大多数逻辑由 ActivityManagerProxy 承担,这里以其中的 getAppTasks 方法为例,在 ActivityManager 中 getAppTasks 方法逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public List<ActivityManager.AppTask> getAppTasks() {
ArrayList<AppTask> tasks = new ArrayList<AppTask>();
List<IBinder> appTasks;
try {
appTasks = getService().getAppTasks(mContext.getPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
int numAppTasks = appTasks.size();
for (int i = 0; i < numAppTasks; i++) {
tasks.add(new AppTask(IAppTask.Stub.asInterface(appTasks.get(i))));
}
return tasks;
}

getService() 其实返回的是一个 IActivityManager,那这个 IActivityManager 的实体类是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

ServiceManager.getService() 返回的是一个系统级的 Service,这个 Service 实际上是 ActivityManagerService,这里也完成创建一个对 ActivityManagerService 的 Client 代理对象 ActivityManagerProxy 实例。ActivityManagerProxy 中的 getAppTasks 方法逻辑就很明确,将数据打包跨进程传递给 Server 端 ActivityManagerService 处理并返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<IAppTask> getAppTasks(String callingPackage) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(callingPackage);
mRemote.transact(GET_APP_TASKS_TRANSACTION, data, reply, 0);
reply.readException();
ArrayList<IAppTask> list = null;
int N = reply.readInt();
if (N >= 0) {
list = new ArrayList<>();
while (N > 0) {
IAppTask task = IAppTask.Stub.asInterface(reply.readStrongBinder());
list.add(task);
N--;
}
}
data.recycle();
reply.recycle();
return list;
}

再来看看 ActivityManagerService 中的 getAppTasks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public List<IAppTask> getAppTasks(String callingPackage) {
int callingUid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
synchronized(this) {
ArrayList<IAppTask> list = new ArrayList<IAppTask>();
try {
if (DEBUG_ALL) Slog.v(TAG, "getAppTasks");
final int N = mRecentTasks.size();
for (int i = 0; i < N; i++) {
TaskRecord tr = mRecentTasks.get(i);
// Skip tasks that do not match the caller. We don't need to verify
// callingPackage, because we are also limiting to callingUid and know
// that will limit to the correct security sandbox.
if (tr.effectiveUid != callingUid) {
continue;
}
Intent intent = tr.getBaseIntent();
if (intent == null ||
!callingPackage.equals(intent.getComponent().getPackageName())) {
continue;
}
ActivityManager.RecentTaskInfo taskInfo =
createRecentTaskInfoFromTaskRecord(tr);
AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid);
list.add(taskImpl);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return list;
}
}

Binder 跨进程通信机制


在 Android 中进程间通信我们通常使用到的是 binder 机制,binder 机制所使用到的四个基本模块是 Binder Client、Binder Server、ServerManager 和 Binder Driver。这四者之间的关系类似与网络访问,Binder Client 相当于我们的客户端 pc , Binder Server 相当于服务器,ServerManager 相当于 DNS 服务器,而 Binder Driver 则相当于一个路由器。其中 Binder Driver 实现在内核空间中,而其余的 3 者 Binder Client、Binder Server、ServerManager 实现在用户空间中。

Binder Client 与 Binder Server 之间的跨进程通信统一通过 Binder Driver 处理转发,对于 Binder Client 来说,其只需要知道自己要使用的 Binder 的名字以及该 Binder 实体在 ServerManager 中的 0 号引用即可,访问原理也比较简单,Binder Client 先是通过 0 号引用去访问 ServerManager 获取 Binder 的引用,得到引用后就可以像普通方法那样调用 Binder 实体方法。最后我们的 ServerManager 则用来管理 Binder Server,Binder Client 可以通过它来查询 Binder Server接口,刚才提到过 Binder Client 可以通过 ServerManager 来获取 Binder 的引用,这个 Binder 引用就是由 ServerManager 来转换的。

可以想象成 Binder Driver 就是一个管道,ServerManager 是一个注册表,所有的 Binder Client 和 Binder Server 都要在它那里注册,Binder Client 也通过 ServerManager 去查找对应的 Binder Server。最后,Binder Client 和 Binder Server 其实实现的接口是一样的,所以大家可以联想到 Binder 机制其实也是一种代理模式。

实战


Notification 适配。通过代理模式解决各个版本的 Notification 碎片化问题,为每种不同的 Notification 样式定义一个类,这里以正常的 64dp Height、256dp Height 和 headsUpContentView 为例,先定义一个抽象类表示通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class Notify {
protected Context context;
protected NotificationManager nm;
protected NotificationCompat.Builder builder;
public Notify(Context context) {
this.context = context;
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(context, "default");
PendingIntent intent = PendingIntent.getActivity(context, 0,
new Intent(context, TestActivity.class),
PendingIntent.FLAG_CANCEL_CURRENT);
builder.setSmallIcon(R.drawable.taiji)
.setContentIntent(intent);
}
public abstract void send(); // 发送一条通知
public abstract void cancel(); // 取消一条通知
}

Notify 声明两个成员变量处理与通知相关的逻辑,且让所有子类共有。两个抽象方法 send 和 cancel 均有具体的子类去实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class NotifyNormal extends Notify {
public NotifyNormal(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPackageName(), R.layout.notify_normal);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cancel(0);
}
}
public class NotifyBig extends Notify {
public NotifyBig(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPackageName(), R.layout.notify_normal);
n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.notify_big);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cancel(0);
}
}
public class NotifyHeadsUp extends Notify {
public NotifyHeadsUp(Context context) {
super(context);
}
@Override
public void send() {
Notification n = builder.build();
n.contentView = new RemoteViews(context.getPackageName(), R.layout.notify_normal);
n.bigContentView = new RemoteViews(context.getPackageName(), R.layout.notify_big);
n.headsUpContentView = new RemoteViews(context.getPackageName(), R.layout.notify_normal);
nm.notify(0, n);
}
@Override
public void cancel() {
nm.cancel(0);
}
}

最后定义一个代理类来整合上面的几个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class NotifyProxy extends Notify {
private Notify notify;
public NotifyProxy(Context context) {
super(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notify = new NotifyHeadsUp(context);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
notify = new NotifyBig(context);
} else {
notify = new NotifyNormal(context);
}
}
@Override
public void send() {
notify.send();
}
@Override
public void cancel() {
notify.cancel();
}
}

在 NotifyProxy 类中定义一个 Notify 类型的成员变量,在构造方法里根据 SDK 版本的不同去实例化不同的 Notify 子类,最终由该类的 send 和 cancel 方法去调用不同的逻辑实现,这样一来,我们的客户端也就是我们的 Activity 类中就很简单了,直接调用 NotifyProxy 中的方法即可。

1
new NotifyProxy(TestActivity.this).send();